package nl.zuiderfestival.raydance.model.net;

import java.io.IOException;
import java.nio.charset.Charset;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Observable;
import java.util.Observer;

import nl.zuiderfestival.raydance.model.zones.Zone;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ArduinoManager extends Observable implements Runnable, Observer {

	private static final Logger log = LoggerFactory.getLogger(ArduinoManager.class);

	private static final int DEFAULT_PORT = 9876;

	private Thread thread;

	private String host;
	private int port;
	private RayProtocol protocol;

	private DatagramSocket client;
	private InetAddress ipAddress;

	private boolean connected;

	public ArduinoManager() {
		this("localhost", DEFAULT_PORT);
	}

	public ArduinoManager(String host, int port) {
		super();
		this.host = host;
		this.port = port;
		protocol = new RayProtocol();
	}

	public int getPort() {
		return port;
	}

	public String getHost() {
		return host;
	}

	public void start() {
		thread = new Thread(this);
		thread.start();
		setChanged();
		notifyObservers();
		log.debug("Started.");
	}

	public void stop() {
		thread = null;
		client = null;
		setConnected(false);
		setChanged();
		notifyObservers();
		log.debug("Stopped.");
	}

	public void run() {
		Thread myThread = Thread.currentThread();
		while (myThread.equals(thread)) {

			if (client == null) {
				try {
					client = new DatagramSocket();
					ipAddress = InetAddress.getByName(host);
					setConnected(true);
					setChanged();
					notifyObservers();
					log.info("Will push commands to {}:{}.", new Object[] {host, port});
				} catch (SocketException e) {
					log.error("SocketException: ", new Object[] {e.getMessage()});
					stop();
				} catch (UnknownHostException e) {
					log.error("UnknownHostException: ", new Object[] {e.getMessage()});
					stop();
				}
			}

			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				stop();
			}

		}
	}

	public boolean isConnected() {
		return this.connected;
	}

	public void setConnected(boolean connected) {
		if (this.connected != connected) {
			setChanged();
		}
		this.connected = connected;
		notifyObservers();
	}

	public void update(Observable o, Object arg) {
		if (o instanceof Zone) {
			if (arg != null) {
				Zone z = (Zone) o;
				log.debug("Send Arduino command zone {} must turn light {}", new Object[] {z, z.isEnabled()?"on":"off"});
				send(z);
			}
		}
	}

	private void send(Zone zone) {
		if (connected) {
			byte[] sendData = protocol.getArduinoCommand(zone).getBytes(Charset.forName("UTF-8"));
			DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, ipAddress, port);
			try {
				log.info("Sending data over UDP: {}", new Object[] {protocol.getArduinoCommand(zone)});
				client.send(sendPacket);
			} catch (IOException e) {
				log.error("IOException: ", new Object[] {e.getMessage()});
			}
		}
	}

	public void updateArduinoConnection(String host, int port) {
		this.host = host;
		this.port = port;
	}

}
